package com.lp.article.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lp.article.mapper.CommentMapper;
import com.lp.article.service.IArticleService;
import com.lp.article.service.ICommentService;
import com.lp.feign.UserClients;
import com.lp.model.config.AppResult;
import com.lp.model.config.CodeMsg;
import com.lp.model.constants.CountType;
import com.lp.model.constants.MQConstants;
import com.lp.model.constants.RedisConstants;
import com.lp.model.dto.article.PageDto;
import com.lp.model.entity.article.Comments;
import com.lp.model.entity.article.redis.CommentsMsg;
import com.lp.model.entity.article.redis.vo.CommentsMsgVO;
import com.lp.model.entity.article.redis.vo.LikesMsgVO;
import com.lp.model.entity.article.vo.ArticleESVO;
import com.lp.model.entity.article.vo.CommentsVO;
import com.lp.model.entity.article.vo.PageVO;
import com.lp.model.entity.user.User;
import com.lp.model.exception.ApplicationException;
import com.lp.model.utils.PageUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyEditorRegistrySupport;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class CommentService implements ICommentService {

    @Autowired
    private CommentMapper commentMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private UserClients userClients;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IArticleService articleService;

    @Override
    public void add(Comments comments) {
        //1.添加评论数据
        commentMapper.add(comments);
        //2.mq 同步评论量 +1
        mqSendCommentCountMsg(comments.getArticleId(), CountType.INCR);
        log.info("用户 {} 在文章 {} 中发布了评论！", comments.getUserId(), comments.getArticleId());
    }

    @Override
    public void remove(Long id) {
        //1.获取评论信息(userId 和 articleId)
        Comments comments = commentMapper.selectCommentById(id);
        if(comments == null) {
            log.info(CodeMsg.FAIL_NULL_COMMENT.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NULL_COMMENT));
        }
        //2.删除评论数据
        commentMapper.remove(id);
        //3.mq 同步评论量 -1
        mqSendCommentCountMsg(comments.getArticleId(), CountType.DECR);
        log.info("用户 {} 在文章 {} 中删除了评论！", comments.getUserId(), comments.getArticleId());
    }

    @Override
    public PageVO<CommentsVO> getCommentVOPage(PageDto page) {
        //1.获取该文章的评论总数
        Long commentTotal = commentMapper.selectCommentTotalByArticleId(page.getId());
        if(commentTotal == null) {
            log.info(CodeMsg.FAIL_ERROR_ARTICLE_ID.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_ARTICLE_ID));
        }
        //2.计算分页数据
        int limit = page.getPageSize();
        int offset = (page.getCurPage() - 1) * page.getPageSize();
        //3.获取评论VO
        List<CommentsVO> commentsVOList = getCommentVOList(page.getId(), limit, offset);
        //4.获取分页数据
        return PageUtils.getPageVO(commentsVOList, commentTotal);
    }

    @Override
    public PageVO<CommentsMsgVO> getMsgPage(PageDto pageDto) {
        int limit = pageDto.getPageSize();
        int offset = (pageDto.getCurPage() - 1) * pageDto.getPageSize();
        //1.从 redis 上获取评论消息分页列表
        //1) 获取 json 列表
        List<String> commentMsgVOPageJson = redisTemplate.opsForList().range(RedisConstants.COMMENT_MSG_LIST + pageDto.getId(),
                offset, limit);
        if(commentMsgVOPageJson == null) {
            return PageUtils.getPageVO(new ArrayList<>(), 0L);
        }
        //2) 转化
        List<CommentsMsgVO> commentsMsgVOPage = getCommentVOPageByJson(commentMsgVOPageJson);
        //3) 获取总数
        Long total = redisTemplate.opsForList().size(RedisConstants.COMMENT_MSG_LIST + pageDto.getId());
        //2.更新用户消息状态(已读)
        redisTemplate.opsForSet().remove(RedisConstants.USER_COMMENT_MSG_STATE_SET,
                pageDto.getId().toString());
        //3.设置过期时间
        redisTemplate.expire(RedisConstants.COMMENT_MSG_LIST + pageDto.getId(),
                RedisConstants.UPDATE_MSG_EXPIRE, TimeUnit.DAYS);
        //4.整合 VO
        return PageUtils.getPageVO(commentsMsgVOPage, total);
    }

    @SneakyThrows
    private List<CommentsMsgVO> getCommentVOPageByJson(List<String> commentMsgVOPageJson) {
        List<CommentsMsgVO> result = new ArrayList<>();
        for(String commentMsgVO : commentMsgVOPageJson) {
            result.add(objectMapper.readValue(commentMsgVO, CommentsMsgVO.class));
        }
        return result;
    }

    @SneakyThrows
    @Override
    public void addMsg(CommentsMsg commentsMsg) {
        //1.在 redis 上增加点赞消息(约定左端消息最新)
        //1) 获取最终 VO
        CommentsMsgVO commentMsgVO = getCommentMsgVO(commentsMsg);
        //2) 存储到 redis
        redisTemplate.opsForList().leftPush(RedisConstants.COMMENT_MSG_LIST + commentMsgVO.getUserId(),
                objectMapper.writeValueAsString(commentMsgVO));
        //2.更新用户消息状态(存不存在都直接添加即可，因为 set 里没有重复元素)
        redisTemplate.opsForSet().add(RedisConstants.USER_COMMENT_MSG_STATE_SET,
                commentMsgVO.getUserId().toString());
        log.info("用户获得了新的点赞! {}", commentsMsg);
    }

    /**
     * 获取评论消息的最终 VO(保存到 redis 上的数据)
     * @param commentsMsg 评论消息 id 相关数据
     */
    private CommentsMsgVO getCommentMsgVO(CommentsMsg commentsMsg) {
        CommentsMsgVO result = new CommentsMsgVO();
        BeanUtils.copyProperties(commentsMsg, result);
        //1.获取 esvo
        ArticleESVO articleESVO = articleService.getArticleESVO(commentsMsg.getPostId());
        if(articleESVO == null) {
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_ARTICLE_ID));
        }
        //3.获取评论信息 TODO
        //4.整合 VO
        result.setUserId(articleESVO.getUserId());
        result.setNickName(articleESVO.getNickname());
        result.setPhoto(articleESVO.getPhotoUrl());
        result.setArticleTitle(articleESVO.getTitle());
        return result;
    }

    @Override
    public void delMsgAll(Long userId) {
        redisTemplate.delete(RedisConstants.COMMENT_MSG_LIST + userId);
    }

    @Override
    public boolean isRead(Long userId) {
        return !Boolean.TRUE.equals(redisTemplate.opsForSet().
                isMember(RedisConstants.USER_COMMENT_MSG_STATE_SET, userId.toString()));
    }

    /**
     * 整合评论VO: 远程调用获取用户信息，并解析，最后整合评论得到 VO
     */
    @SneakyThrows
    private List<CommentsVO> getCommentVOList(Long articleId, int limit, int offset) {
        //1.该文章的获取评论信息(分页)
        List<Comments> commentsList = commentMapper.selectCommentListByArticleId(articleId, limit, offset);
        List<CommentsVO> commentsVOList = new ArrayList<>();
        for(Comments comments : commentsList) {
            CommentsVO commentsVO = new CommentsVO();
            BeanUtils.copyProperties(comments, commentsVO);
            //2.远程调用获取用户数据，并解析
            String infoJson = userClients.info(comments.getUserId());
            AppResult result = objectMapper.readValue(infoJson, AppResult.class);
            User user = objectMapper.convertValue(result.getData(),
                    new TypeReference<User>() {});
            //3.整合到 VO 中
            commentsVO.setNickname(user.getNickname());
            commentsVO.setPhotoUrl(user.getPhotoUrl());
            commentsVOList.add(commentsVO);
        }
        return commentsVOList;
    }

    /**
     *  mq 发送评论量自增或自减消息
     * @param articleId
     * @param type 自增或自减
     */
    @SneakyThrows
    private void mqSendCommentCountMsg(Long articleId, String type) {
        Map<String, Object> map = new HashMap<>();
        map.put("articleId", articleId);
        map.put("type", type);
        String msg = objectMapper.writeValueAsString(map);
        rabbitTemplate.convertAndSend(MQConstants.COMMENT_EXCHANGE_DIRECT,
                MQConstants.COMMENT_INCR_OR_DECR_QUEUE, msg);
    }

}
